package com.wirelesspienetwork.overview.views;

import com.wirelesspienetwork.overview.misc.OverviewConfiguration;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.components.Component;
import ohos.agp.components.VelocityDetector;
import ohos.agp.window.service.DisplayAttributes;
import ohos.agp.window.service.DisplayManager;
import ohos.multimodalinput.event.TouchEvent;

public class SwipeHelper {

    public static final int X = 0;
    public static final int Y = 1;

    private static int sLinearInterpolator = Animator.CurveType.LINEAR;

    private static final float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
    private  static final int DEFAULT_ESCAPE_ANIMATION_DURATION = 75; // ms
    private  static final int MAX_ESCAPE_ANIMATION_DURATION = 350; // ms
    private  static final int MAX_DISMISS_VELOCITY = 2000; // dp/sec
    private static final int SNAP_ANIM_LEN = 250; // ms

    public static float ALPHA_FADE_START = 0.15f; // fraction of thumbnail width
    // where fade starts
    static final float ALPHA_FADE_END = 0.65f; // fraction of thumbnail width
    // beyond which alpha->0
    private float mMinAlpha = 0f;

    private float mPagingTouchSlop;
    Callback mCallback;
    private int mSwipeDirection;
    private VelocityDetector mVelocityTracker;

    private float mInitialTouchPos;
    private boolean mDragging;

    private Component mCurrView;
    private float mDensityScale;

    public boolean mAllowSwipeTowardsStart = true;
    public boolean mAllowSwipeTowardsEnd = true;

    OverviewConfiguration mConfig;

    public SwipeHelper(int swipeDirection, Callback callback, float densityScale,
                       float pagingTouchSlop, OverviewConfiguration config) {
        mCallback = callback;
        mSwipeDirection = swipeDirection;
        mVelocityTracker = VelocityDetector.obtainInstance();
        mDensityScale = densityScale;
        mPagingTouchSlop = pagingTouchSlop;
        mConfig = config;
    }

    public float getPos(TouchEvent ev) {
        return mSwipeDirection == X ? ev.getPointerPosition(0).getX() : ev.getPointerPosition(0).getY();
    }

    private float getTranslation(Component v) {
        return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
    }

    private float getVelocity(VelocityDetector vt) {
        return mSwipeDirection == X ? vt.getHorizontalVelocity():
                vt.getVerticalVelocity();
    }

    private AnimatorProperty createTranslationAnimation(Component v, float newPos) {
        AnimatorProperty animatorProperty;
        if (mSwipeDirection == X) {
            animatorProperty = v.createAnimatorProperty().moveFromX(0).moveToX(newPos);
        } else {
            animatorProperty = v.createAnimatorProperty().moveFromY(0).moveToY(newPos);
        }
        return animatorProperty;
    }

    private float getPerpendicularVelocity(VelocityDetector vt) {
        return mSwipeDirection == X ? vt.getVerticalVelocity() :
                vt.getHorizontalVelocity();
    }

    private void setTranslation(Component v, float translate) {
        if (mSwipeDirection == X) {
            v.setTranslationX(translate);
        } else {
            v.setTranslationY(translate);
        }
    }

    private float getSize(Component v) {
        DisplayAttributes displayAttributes = DisplayManager.getInstance().getDefaultDisplay(v.getContext()).get().getAttributes();

       // final DisplayMetrics dm = v.getContext().getResources().getDisplayMetrics();
        return mSwipeDirection == X ? displayAttributes.width : displayAttributes.height;
    }

    public void setMinAlpha(float minAlpha) {
        mMinAlpha = minAlpha;
    }

    float getAlphaForOffset(Component view) {
        float viewSize = getSize(view);
        final float fadeSize = ALPHA_FADE_END * viewSize;
        float result = 1.0f;
        float pos = getTranslation(view);
        if (pos >= viewSize * ALPHA_FADE_START) {
            result = 1.0f - (pos - viewSize * ALPHA_FADE_START) / fadeSize;
        } else if (pos < viewSize * -ALPHA_FADE_START) {
            result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize;
        }
        result = Math.min(result, 1.0f);
        result = Math.max(result, 0f);
        return Math.max(mMinAlpha, result);
    }

    public boolean onInterceptTouchEvent(TouchEvent ev) {
        final int action = ev.getAction();

        switch (action) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                mDragging = false;
                mCurrView = mCallback.getChildAtPosition(ev);
                mVelocityTracker.clear();
                if (mCurrView != null) {
                    mVelocityTracker.addEvent(ev);
                    mInitialTouchPos = getPos(ev);
                }
                break;
            case TouchEvent.POINT_MOVE:
                if (mCurrView != null) {
                    mVelocityTracker.addEvent(ev);
                    float pos = getPos(ev);
                    float delta = pos - mInitialTouchPos;
                    if (Math.abs(delta) > mPagingTouchSlop) {
                        mCallback.onBeginDrag(mCurrView);
                        mDragging = true;
                        mInitialTouchPos = pos - getTranslation(mCurrView);
                    }
                }
                break;
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                mDragging = false;
                mCurrView = null;
                break;
        }
        return mDragging;
    }

    /** The view to be dismissed
     * @param view The view to be dismissed
     * @param velocity The desired pixels/second speed at which the view should move
     */
    private void dismissChild(final Component view, float velocity) {
        final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
        float newPos;
        if (velocity < 0
                || (velocity == 0 && getTranslation(view) < 0)
                // if we use the Menu to dismiss an item in landscape, animate up
                || (velocity == 0 && getTranslation(view) == 0 && mSwipeDirection == Y)) {
            newPos = -getSize(view);
        } else {
            newPos = getSize(view);
        }
        int duration = MAX_ESCAPE_ANIMATION_DURATION;
        if (velocity != 0) {
            duration = Math.min(duration,
                    (int) (Math.abs(newPos - getTranslation(view)) *
                            1000f / Math.abs(velocity)));
        } else {
            duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
        }

        AnimatorProperty anim = createTranslationAnimation(view, newPos);
        anim.setCurveType(sLinearInterpolator);
        anim.setDuration(300);
        anim.alpha(getAlphaForOffset(view));
        anim.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                mCallback.onChildDismissed(view);
                if (canAnimViewBeDismissed) {
                    view.setAlpha(1.f);
                }
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }
        }).start();
        anim.start();
    }

    private void snapChild(final Component view, float velocity) {
        final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
        AnimatorProperty animatorProperty = new AnimatorProperty();
        int duration = SNAP_ANIM_LEN;
        animatorProperty.setDuration(duration);
        animatorProperty.setCurveType(mConfig.linearOutSlowInInterpolator);
        animatorProperty.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                if (canAnimViewBeDismissed) {
                    view.setAlpha(1.0f);
                }
                mCallback.onSnapBackCompleted(view);
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {
                if (canAnimViewBeDismissed) {
                    view.setAlpha(getAlphaForOffset(view));
                }
                mCallback.onSwipeChanged(mCurrView, view.getTranslationX());
            }
        }).start();

    }

    public boolean onTouchEvent(TouchEvent ev, int mInitialTouchPos, Component mCurrView) {
        mVelocityTracker.addEvent(ev);
        final int action = ev.getAction();
        switch (action) {

            case TouchEvent.HOVER_POINTER_MOVE:
            case TouchEvent.POINT_MOVE:
                if (mCurrView != null) {
                    float delta = getPos(ev) - mInitialTouchPos;
                    setSwipeAmount(delta, mCurrView);
                    mCallback.onSwipeChanged(mCurrView, delta);
                }
                break;
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                if (mCurrView != null) {
                    float delta = getPos(ev) - mInitialTouchPos;
                    setSwipeAmount(delta, mCurrView);
                    mCallback.onSwipeChanged(mCurrView, delta);
                }
                if (mCurrView != null) {
                    endSwipe(mVelocityTracker, mCurrView);
                }
                mCurrView = null;
                break;
        }
        return true;
    }

    private void setSwipeAmount(float amount, Component mCurrView) {
        // don't let items that can't be dismissed be dragged more than
        // maxScrollDistance
        if (!isValidSwipeDirection(amount)) {
            float size = getSize(mCurrView);
            float maxScrollDistance = 0.15f * size;
            if (Math.abs(amount) >= size) {
                amount = amount > 0 ? maxScrollDistance : -maxScrollDistance;
            } else {
                amount = maxScrollDistance * (float) Math.sin((amount/size)*(Math.PI/2));
            }
        }
        setTranslation(mCurrView, amount);
        float alpha = getAlphaForOffset(mCurrView);
        mCurrView.setAlpha(alpha);
    }

    private boolean isValidSwipeDirection(float amount) {
        if (mSwipeDirection == X) {
            return (amount <= 0) ? mAllowSwipeTowardsStart : mAllowSwipeTowardsEnd;
        }
        // Vertical swipes are always valid.
        return true;
    }

    public void endSwipe(VelocityDetector velocityTracker, Component mCurrView) {
        float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale;
        velocityTracker.calculateCurrentVelocity(1000 /* px/sec */, maxVelocity, maxVelocity);
        float velocity = getVelocity(velocityTracker);
        float perpendicularVelocity = getPerpendicularVelocity(velocityTracker);
        float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
        float translation = getTranslation(mCurrView);
        // Decide whether to dismiss the current view
        boolean childSwipedFarEnough = Math.abs(translation) > 0.6 * getSize(mCurrView);
        boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
                (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
                (velocity > 0) == (translation > 0);

        boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
                && isValidSwipeDirection(translation)
                && (childSwipedFastEnough || childSwipedFarEnough);
        // flingadingy
        dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);
    }

    public interface Callback {
        Component getChildAtPosition(TouchEvent ev);

        boolean canChildBeDismissed(Component v);

        void onBeginDrag(Component v);

        void onSwipeChanged(Component v, float delta);

        void onChildDismissed(Component v);

        void onSnapBackCompleted(Component v);

        void onDragCancelled(Component v);
    }
}
